package Multithread.Thread;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

import static java.lang.Thread.sleep;

/**
 * 4 多线程锁
 * 4.1 synchronized在对象上的应用
 * 4.2 synchronized在方法上的应用
 * 4.3 wait和synchronized的使用
 * 4.4 park和unpark的使用
 * 4.5 ReentrantLock锁
 */

@Slf4j
public class Thread04 {

    static  int counter = 0;
    static final Object room = new Object();

    static ReentrantLock lock = new ReentrantLock();


    public static void main(String[] args) throws InterruptedException {

//        4.1 synchronized在对象上的应用
//        fun01();

//        4.2 synchronized在方法上的应用
//        fun021();

//        4.3 wait和synchronizedl的使用
//        fun03();

//        4.4 park和unpark的使用
//        fun041();
//        fun042();
//        4.5.1 ReentrantLock锁
        fun051();

    }

    /**
     * 4.1 synchronized在对象上的应用
     * synchronized，来解决上述问题，即俗称的【对象锁】，它采用互斥的方式让同一
     * 时刻至多只有一个线程能持有【对象锁】，其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁
     * 的线程可以安全的执行临界区内的代码，不用担心线程上下文切换
     */
    public static void fun01() throws InterruptedException {

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (room) {  //锁对象可以是任意的
                    counter++;
                }
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (room) {
                    counter--;
                }
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter);
    }

    /**
     * 4.2.1 测试synchronized在方法上的应用
     */
    public static void fun021() throws InterruptedException {

        Thread04 thread04 = new Thread04();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                thread04.fun022(1);
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                thread04.fun022(-1);
            }
        }, "t2");

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("counter is : " + thread04.counter);

    }

    /**
     * 4.2.2 synchronized在方法上的应用
     * 如果加static，则锁对象是类的字节码文件Thread04.class
     * 如果不加static，则锁对象是对象本身 this
     */
    public static synchronized void fun022(int i){
        counter += i;
    }

    /**
     * 4.3 wait和synchronized的使用
     * sleep 是 Thread 方法，而 wait 是 Object 的方法
     * sleep 不需要强制和 synchronized 配合使用，但 wait 需要和 synchronized 一起用
     * sleep 在睡眠的同时，不会释放对象锁的，但 wait 在等待的时候会释放对象锁
     * 它们状态 ：TIMED_WAITING
     * notify() 随机唤醒obj上一个线程
     * notifyAll() 唤醒obj上所有等待线程
     */
    public static void fun03() throws InterruptedException {

        new Thread(() -> {
            synchronized (room) {
                System.out.println("随机线程 1 执行....");
                try {
                    room.wait(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("随机线程 1 其它代码....");
            }
        }).start();

        new Thread(() -> {
            synchronized (room) {
                System.out.println("随机线程 2 执行....");
                try {
                    room.wait(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("随机线程 2 其它代码....");
            }
        }).start();

        // 主线程两秒后执行
        sleep(2);

        System.out.println("唤醒 room 上其它线程");

        synchronized (room) {
            room.notify(); // 唤醒obj上一个线程
//            room.notifyAll(); // 唤醒obj上所有等待线程
        }

    }

    /**
     * 4.4.1 park和unpark的使用（先park后unpark）
     * 它们是 LockSupport 类中的方法
     * wait，notify 和 notifyAll 必须配合 Object Monitor 一起使用，而 park，unpark 不必
     * park&unpark 是以线程为单位来【阻塞】和【唤醒】线程，而 notify 只能随机唤醒一个等待线程，notifyAl是唤醒所有等待线程，就不那么【精确】
     * park & unpark 可以先 unpark，而 wait & notify 不能先 notify
     */
    public static void fun041() throws InterruptedException {

        Thread t1 = new Thread(() -> {
            log.info("start...");
            try {
                sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.info("park...");
            LockSupport.park();
            log.info("resume...");

        },"t1");

        t1.start();
        sleep(2);
        log.info("unpark...");

        LockSupport.unpark(t1);

    }

    /**
     * 4.4.2 park和unpark的使用（先unpark后park）
     */
    public static void fun042() throws InterruptedException {

        Thread t1 = new Thread(() -> {
//            log.debug("start...");
            try {
                sleep(2);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
//            log.debug("park...");
            LockSupport.park();
//            log.debug("resume...");

        },"t1");

        t1.start();
        sleep(1);
//        log.debug("unpark...");

        LockSupport.unpark(t1);

    }

//    4.5.1 ReentrantLock锁
    public static void fun051() {
    lock.lock();
    try {
        System.out.println("execute method1");
        fun052();
    } finally {
        lock.unlock();
    }
}

//    4.5.2 ReentrantLock锁
    public static void fun052() {
        lock.lock();
        try {
            System.out.println("execute method2");
            fun053();
        } finally {
            lock.unlock();
        }
    }

//    4.5.3 ReentrantLock锁
    public static void fun053() {
        lock.lock();
        try {
            System.out.println("execute method3");
        } finally {
            lock.unlock();
        }
    }
}
